package com.travel.search.service.Impl;

import com.alibaba.fastjson.JSON;
import com.travel.common.es.AttractionEsModel;
import com.travel.search.config.TravelElasticsearchConfig;
import com.travel.search.constant.EsConstant;
import com.travel.search.service.TravelAttractionSearchService;
import com.travel.search.vo.SearchAttractionParam;
import com.travel.search.vo.SearchAttractionResult;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

@Service
public class TravelAttractionSearchServiceImpl implements TravelAttractionSearchService {

    @Autowired
    RestHighLevelClient esRestClient;

    /**
     * @param param：所有的查询条件和过滤条件
     * @return
     */
    @Override
    public SearchAttractionResult search(SearchAttractionParam param) {
        //1. 动态构建出查询需要的DSL语句
        SearchAttractionResult result = null;

        //2. 准备检索请求DSL语句
        SearchRequest searchRequest = buildSearchAttractionDSL(param);


        try{
            //3. 执行检索请求
            SearchResponse response = esRestClient.search(searchRequest, TravelElasticsearchConfig.COMMON_OPTIONS);

            //4. 分析响应数据，封装成我们需要的格式
            result = buildSearchResult(response,param);
        }catch (IOException e){
            e.printStackTrace();
        }

        return result;

    }


    /**
     * 构建DSL语句
     * 模糊匹配，完成排序，分页，高亮
     * 过滤（按照各种属性），聚合分析功能
     * @param param
     * @return
     */
    private SearchRequest buildSearchAttractionDSL(SearchAttractionParam param){
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //构建DSL语句的

        //1. 构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //1.1 bool - must 模糊匹配 name
        if(!StringUtils.isEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("name",param.getKeyword()));
        }

        //1.2 bool - filter 按照省份过滤
        if(!StringUtils.isEmpty(param.getLocation())){
            boolQuery.filter(QueryBuilders.termQuery("location",param.getLocation()));
        }

        //1.3 bool - filter 按照景点标签过滤
        if(!StringUtils.isEmpty(param.getTag())){
            boolQuery.filter(QueryBuilders.termQuery("tag",param.getTag()));
        }

        //把以前所有的条件都拿来进行封装
        sourceBuilder.query(boolQuery);

        /**
         * 排序，分页，高亮
         */

        //2.1 排序
        if(!StringUtils.isEmpty(param.getSort())){
            //例：sort=hotScore_asc/desc
            String sort = param.getSort();
            String[] s = sort.split("_"); //s[0]：排序排序，s[1]：升/降
            SortOrder order = s[1].equalsIgnoreCase("asc")? SortOrder.ASC:SortOrder.DESC;
            sourceBuilder.sort(s[0],order);
        }

        //2.2 分页 pageSize  pageNum  from  from=(pageNum-1)*pageSize
        sourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        //2.3 高亮
        if(!StringUtils.isEmpty(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("name");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }

        /**
         * 聚合分析
         * .size()：指明该参数最多的个数
         */
        //1. 景点标签聚合
        TermsAggregationBuilder tag_agg = AggregationBuilders.terms("agg_tag").field("tag");
        tag_agg.size(10);

        //2. 省份聚合
        TermsAggregationBuilder location_agg = AggregationBuilders.terms("agg_location").field("location");
        location_agg.size(23);

        sourceBuilder.aggregation(tag_agg);
        sourceBuilder.aggregation(location_agg);

        System.out.println("构建的DSL语句=="+sourceBuilder.toString());

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
        return searchRequest;

    }

    /**
     * 构建结果数据
     * @param response
     * @param param
     * @return
     */
    public SearchAttractionResult buildSearchResult(SearchResponse response, SearchAttractionParam param){
        SearchAttractionResult result = new SearchAttractionResult();

        //1. 返回所有查询到的景点信息
        SearchHits hits = response.getHits();
        List<AttractionEsModel> esModels = new ArrayList<>();
        if(hits.getHits()!= null && hits.getHits().length>0){
            for (SearchHit hit : hits) {
                // 为景点信息
                String sourceAsString = hit.getSourceAsString();
                // 转为景点对象
                AttractionEsModel esModel = JSON.parseObject(sourceAsString, AttractionEsModel.class);
                // 设置检索词高亮
                if(!StringUtils.isEmpty(param.getKeyword())){
                    HighlightField name = hit.getHighlightFields().get("name");
                    String string = name.getFragments()[0].string();
                    esModel.setName(string);
                }
                esModels.add(esModel);
            }
        }
        result.setAttractions(esModels);

        //2. 当前所有景点涉及到的景点标签
        List<String> tags = new ArrayList<>();
        ParsedStringTerms tag_agg = response.getAggregations().get("agg_tag");
        for (Terms.Bucket bucket : tag_agg.getBuckets()) {
            String tag = bucket.getKeyAsString();
            tags.add(tag);
        }
        result.setTags(tags);


        //3. 当前所有景点涉及到的省份
        List<String> locations = new ArrayList<>();
        ParsedStringTerms location_agg = response.getAggregations().get("agg_location");
        for (Terms.Bucket bucket : location_agg.getBuckets()) {
            String location = bucket.getKeyAsString();
            locations.add(location);
        }
        result.setLocations(locations);

        //4. 分页信息 - 页码
        result.setPageNum(param.getPageNum());

        //5. 分页信息 - 总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);

        //6. 分页信息 - 总页码
        int totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE == 0?(int)total/EsConstant.PRODUCT_PAGESIZE:(int)total/EsConstant.PRODUCT_PAGESIZE+1;
        result.setTotalPages(totalPages);

        //7. 构建面包屑导航功能 省份，景点标签 选中则不再显示该属性的其它值
        //keyword=九寨沟&sort=hotScore_desc&location=四川&tag=waters
        List<SearchAttractionResult.NavVo> navs = new ArrayList<>();
        if(param.getTag() != null){
            SearchAttractionResult.NavVo navVo = new SearchAttractionResult.NavVo();
            navVo.setNavName("景点标签");
            navVo.setNavValue(param.getTag());

            String replace = replaceQueryString(param, param.getTag(),"tag");

            //设置新的路径url
            navVo.setLink("http://search.travel.com/attractionList.html?"+replace);
            navs.add(navVo);
        }

        if(param.getLocation() != null){
            SearchAttractionResult.NavVo navVo = new SearchAttractionResult.NavVo();
            navVo.setNavName("省份");
            navVo.setNavValue(param.getLocation());

            String replace = replaceQueryString(param, param.getLocation(),"location");

            //设置新的路径url
            navVo.setLink("http://search.travel.com/attractionList.html?"+replace);
            navs.add(navVo);
        }

        result.setNavs(navs);

        //设置检索的关键词
        result.setKeyword(param.getKeyword());


        return result;
    }


    /**
     * 将请求url中的某个查询参数删除
     * @param param：查询参数实例
     * @param value：要删除的参数值
     * @param key：要删除的参数名
     * @return
     */
    private String replaceQueryString(SearchAttractionParam param, String value, String key) {
        String encode = null;
        try{
            //将属性以utf-8编码格式转为string
            encode = URLEncoder.encode(value, "UTF-8");
            encode = encode.replace("+","%20");
        }catch (UnsupportedEncodingException e){
            e.printStackTrace();
        }
        return param.get_queryString().replace("&"+key+"=" + encode, "");
    }
}
